/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * The software in this package is published under the terms of the GPL v2.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE-GPL.txt file.
 */

package org.cemantika.aspects;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import org.cemantika.contextconsumer.ContextConsumerAdapter;
import org.cemantika.exception.AddFocusException;
import org.cemantika.infrastructure.model.ContextUser;
import org.cemantika.management.ContextManager;
import org.cemantika.metamodel.structure.Focus;
import org.cemantika.metamodel.structure.RelevanceAssociation;
import org.cemantika.model.ContextConsumerModel;
import org.cemantika.util.CemantikaLogger;

/**
 * FocusRegistration represents the aspect that tests if the {@link Focus} is activated or not. If
 * it is active, the system act naturally, otherwise the system do not add the {@link Focus}.
 * 
 * @author andersonfellipe
 */
public aspect FocusRegistration {

    /**
     * Represents the execution of the method addFocus, from the class {@link ContextManager}.
     * 
     * @param obj the object of the {@link Focus} to add.
     */
    pointcut addFocus(ContextConsumerAdapter obj): 
        execution(void ContextManager.addFocus(ContextConsumerAdapter, ..)) && 
        args(obj, ..);

    /**
     * Represents the execution of the method removeFocus, from the class {@link ContextManager}.
     * 
     * @param obj the object of the {@link Focus} to remove.
     * @param contextUser the object of a {@link ContextUser}.
     */
    pointcut removeFocus(ContextConsumerAdapter obj, ContextUser contextUser): 
        execution(void ContextManager.removeFocusFromContextualManipulation(ContextConsumerAdapter, ContextUser)) && 
        args(obj, contextUser);

    /**
     * Represents the around advice that checks the focus activation. If the focus is activated it
     * registers the contextual entities of the focus and registers the contextual elements related
     * to these contextual entities.
     * 
     * @param obj the object of the {@link Focus} to add.
     */
    @SuppressWarnings("restriction")
    void around(ContextConsumerAdapter obj): addFocus(obj) {
        try {
            ContextManager contextManager = ContextManager.getInstance();
            Annotation classAnnotation = obj.getClass().getAnnotation(Focus.class);
            if (classAnnotation instanceof Focus) {
                Focus focus = (Focus) classAnnotation;
                if (focus.isActivated()) {
                    contextManager.registerFocusActivation(obj.getClass().getSimpleName(), true);
                    Method[] methods = obj.getClass().getMethods();
                    List<String> elements = new LinkedList<String>();
                    for (Method method : methods) {
                        Annotation methodAnnotation = method.getAnnotation(RelevanceAssociation.class);
                        if (methodAnnotation instanceof RelevanceAssociation) {
                            RelevanceAssociation relevanceAssociation = (RelevanceAssociation) methodAnnotation;
                            if (!relevanceAssociation.element().isEmpty()) {
                                CemantikaLogger.logger.info("** Registration of context consumer "
                                        + obj.getClass().getSimpleName() + " to contextual element: "
                                        + relevanceAssociation.element());
                                elements.add(relevanceAssociation.element());
                            }
                        }
                    }
                    ContextConsumerModel contextConsumer = new ContextConsumerModel(obj, elements);
                    contextManager.addContextConsumer(contextConsumer);
                    CemantikaLogger.logger.info("** Focus added: " + obj.getClass().getSimpleName());
                    proceed(obj);
                } else {
                    CemantikaLogger.logger.warn("** Focus was not registered because it is not active: "
                            + obj.getClass().getCanonicalName());
                    contextManager.registerFocusActivation(obj.getClass().getSimpleName(), false);
                }
            } else {
                AddFocusException err = new AddFocusException();
                CemantikaLogger.logger.error("** " + err.getMessage());
                throw err;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Represents the around advice that checks if the focus is registered to be removed.
     * 
     * @param obj the object of the {@link Focus} to remove.
     */
    @SuppressWarnings("restriction")
    void around(ContextConsumerAdapter obj, ContextUser contextUser): removeFocus(obj, contextUser) {
        ContextManager contextManager = ContextManager.getInstance();
        if (contextManager.isFocusRegistered(obj, contextUser)) {
            proceed(obj, contextUser);
            ContextConsumerModel contextConsumer = new ContextConsumerModel(obj);
            contextManager.removeContextConsumer(contextConsumer);
            CemantikaLogger.logger.info("** Focus removed: " + obj.getClass().getSimpleName());
        } else {
            CemantikaLogger.logger.warn("** Focus was not removed because it is not registered: "
                    + obj.getClass().getCanonicalName());
        }
    }

}
